package com.bootdo.common.utils.encryptionUtil;

import com.bootdo.common.utils.encryptionUtil.Seal.bytearray;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.zip.ZipOutputStream;

import static com.bootdo.common.utils.encryptionUtil.FileEncryption.ENCFILE_HEAD_LEN;
import static com.bootdo.common.utils.encryptionUtil.Header.MAX_CIPHER_MAIN_FLAG;
import static com.bootdo.common.utils.encryptionUtil.Header.gConstCipherFlagBuffer;
import static com.bootdo.common.utils.encryptionUtil.Seal.InitEncryptKey;

public class FileDecryption {
    static int BUFFER_SIZE = 0x10000;

    /**
     * 解密文件的操作，返回值为int类型，根据返回值的不同，接收不同的结果
     * 1：文件正常解密
     * -1：文件没有找到
     * -2：加密文件至少有1024字节，源文件少于这个长度
     * -3：源文件没有包含加密时放入的密码，即不是采用本系统进行加密
     * -4：在写入文件时出现的其他异常
     *
     * @param originalFile
     * @param targetFile
     * @param ouputStream
     * @param IN
     * @param bins
     * @return
     * @throws Exception
     */
    public static int DecryptFile(File originalFile, File targetFile, String name, HttpServletResponse response, ZipOutputStream ouputStream, FileInputStream IN, BufferedInputStream bins) throws Exception {
        RandomAccessFile sourceFile = null;
        Seal seal = new Seal();
        Seal.seal_ctx sc = seal.new seal_ctx();
        InitEncryptKey(seal, sc);
        PCIPHER_HEADER pHeader = new Header().BuildCipherHeader();

        //-------------下面是解密文件的测试
        String source = "";
        String destination = "";
        try {
            //加密文件路径
            source = originalFile.getPath();
            //解密文件路径
            destination = targetFile.getPath();
            sourceFile = new RandomAccessFile(source, "r");
        } catch (Exception e) {
            //文件没有找到
            return -1;
        }


        long nFileLen = sourceFile.length();

        //加密文件的长度至少是1024字节长度！
        if (nFileLen <= ENCFILE_HEAD_LEN) {
            sourceFile.close();
            return -2;
        }

        //-----------加密文件权限判断-----------------
        /**
         * -----------！----------！注意！----------！----------
         * 这一条有点不明所以，为什么要从213往后开始读取？
         * 所以我对此进行了修改，从0读到1024，寻找其中是否含有这个字符
         */
        /*int nRightKeyPos = 213;
        byte[] cRightInfo1 = new byte[30];
        byte[] cRightInfo2 = new byte[30];
        String sRightInfoList = "moqf";  // 这是一个后台传过来的串,不是固定值,实际根据协议传什么
        sourceFile.seek(nRightKeyPos);
        sourceFile.read(cRightInfo1);

        int idx = 0;
        for (int i = 0; i < 30; i++)
        {
            if (cRightInfo1[i] != 0)
            {
                cRightInfo2[idx++] = cRightInfo1[i];
            }
        }*/
        /*byte[] cRightInfo1 = new byte[1024];
        byte[] cRightInfo2 = new byte[1024];
        //String sRightInfoList = "moqf";
        sourceFile.read(cRightInfo1);
        int idx = 0;
        for (int i = 0; i < 1024; i++)
        {
            if (cRightInfo1[i] != 0)
            {
                cRightInfo2[idx++] = cRightInfo1[i];
            }
        }*/

        //表示两个32位的头
        byte[] MainFlag = new byte[MAX_CIPHER_MAIN_FLAG];
        byte[] FlagEncData = new byte[MAX_CIPHER_MAIN_FLAG];
        byte[] unknown = new byte[149];

        sourceFile.read(MainFlag);
        sourceFile.seek(32);
        sourceFile.read(FlagEncData);
        sourceFile.seek(64);
        sourceFile.read(unknown);

        int dwRetVal = 32;

        bytearray HEADER_FLAGENCDATA = seal.new bytearray(MAX_CIPHER_MAIN_FLAG);
        for (int i = 0; i < MAX_CIPHER_MAIN_FLAG; i++) {
            HEADER_FLAGENCDATA.a[i] = gConstCipherFlagBuffer.a[i];
        }
        seal.seal_encrypt(sc, HEADER_FLAGENCDATA, dwRetVal, 0x00);


        //对两个开头进行判断
        for (int i = 0; i < MAX_CIPHER_MAIN_FLAG; i++) {
            if (MainFlag[i] != gConstCipherFlagBuffer.a[i]) {
                return -3;
            }

            if (FlagEncData[i] != HEADER_FLAGENCDATA.a[i]) {
                return -3;
            }
        }

        /**
         * 原本的代码在这里会出现乱码，直接return，所以进行改正
         * 查明原因，这个是解密的代码，需要文件头部包含sRightInfoList这个字符串的值才能解密
         *
         * 2020.03.27 未来修改
         * 这个indexOf的判定有点奇怪，sRightInfoList是一个小的字符串，sRightInfo是大的字符串
         * 应该从大的字符串中寻找小的
         * 所以进行了修改，把两个变量的位置替换了一下
         *
         * 2020.04.01 未来修改
         * 加入新的判断
         * 1.判断前32位是否是规定的头
         * 2.判断33位到64位是否是加密过的头
         * 3.判断key是否对应
         */

        /*String sRightInfo = new String(cRightInfo2);
        sRightInfo = sRightInfo.substring(0, idx);
        if (sRightInfo.length() > 0)
        {
            if (sRightInfo.indexOf(sRightInfoList) == -1)
            {
                //源文件没有包含对应的密码
                sourceFile.close();
                return -3;
            }

        }*/

        try {
           /* OutputStream fos = new FileOutputStream(destination);
            BufferedOutputStream bos = new BufferedOutputStream(fos, BUFFER_SIZE);*/
            sourceFile.seek(ENCFILE_HEAD_LEN);


            long nLeftLen = nFileLen - ENCFILE_HEAD_LEN;
            long OffsetValue = ENCFILE_HEAD_LEN;
            bytearray FileBuf = seal.new bytearray(BUFFER_SIZE);
            byte[] bytes = new byte[BUFFER_SIZE];
            if(null == ouputStream ) {
                // 设置response的Header
                response.setHeader("Content-Disposition", "attachment;filename=" + new String(name.getBytes("utf-8"),"ISO8859-1"));
                response.setHeader("Content-Length", "" + (originalFile.length() - 1024));
                response.setContentType("application/octet-stream");
            }
            OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
            while (nLeftLen > 0) {
                int nRead = sourceFile.read(bytes);
                if (nRead <= 0)
                    break;

                nLeftLen = nLeftLen - nRead;

                for (int i = 0; i < nRead; i++)
                    FileBuf.a[i] = bytes[i];

                seal.seal_decrypt(sc, FileBuf, nRead, OffsetValue);
                for (int i = 0; i < nRead; i++)
                    bytes[i] = FileBuf.a[i];

                /*bos.write(bytes, 0, nRead);
                bos.flush();*/

                if(null != ouputStream) {
                    ouputStream.write(bytes);
                }else {
                    toClient.write(bytes);
                    toClient.flush();
                }

                OffsetValue += nRead;
            }
            if(null != ouputStream) {
                //关闭创建的流对象
                bins.close();
                IN.close();
            }

            sourceFile.close();
            toClient.close();
            /*bos.close();
            fos.close();*/

            return 1;
        } catch (Exception e) {
            e.printStackTrace();
            return -4;
        }
    }

    public static void main(String[] args) throws Exception {
       File file = new File("C:\\var\\ENCRY_c22964b220fd4e678955fe029c862975监护人APP测试问题20201214.docx");
        int flag = DecryptFiles(file, new File("C:\\var\\监护人APP测试问题20201214.docx"));
        System.out.println(flag);
    }

    public static int DecryptFiles(File originalFile, File targetFile) throws Exception {
        RandomAccessFile sourceFile = null;
        Seal seal = new Seal();
        Seal.seal_ctx sc = seal.new seal_ctx();
        InitEncryptKey(seal, sc);
        PCIPHER_HEADER pHeader = new Header().BuildCipherHeader();

        //-------------下面是解密文件的测试
        String source = "";
        String destination = "";
        try {
            //加密文件路径
            source = originalFile.getPath();
            //解密文件路径
            destination = targetFile.getPath();
            sourceFile = new RandomAccessFile(source, "r");
        } catch (Exception e) {
            //文件没有找到
            return -1;
        }


        long nFileLen = sourceFile.length();

        //加密文件的长度至少是1024字节长度！
        if (nFileLen <= ENCFILE_HEAD_LEN) {
            sourceFile.close();
            return -2;
        }

        //-----------加密文件权限判断-----------------
        /**
         * -----------！----------！注意！----------！----------
         * 这一条有点不明所以，为什么要从213往后开始读取？
         * 所以我对此进行了修改，从0读到1024，寻找其中是否含有这个字符
         */
        /*int nRightKeyPos = 213;
        byte[] cRightInfo1 = new byte[30];
        byte[] cRightInfo2 = new byte[30];
        String sRightInfoList = "moqf";  // 这是一个后台传过来的串,不是固定值,实际根据协议传什么
        sourceFile.seek(nRightKeyPos);
        sourceFile.read(cRightInfo1);

        int idx = 0;
        for (int i = 0; i < 30; i++)
        {
            if (cRightInfo1[i] != 0)
            {
                cRightInfo2[idx++] = cRightInfo1[i];
            }
        }*/
        /*byte[] cRightInfo1 = new byte[1024];
        byte[] cRightInfo2 = new byte[1024];
        //String sRightInfoList = "moqf";
        sourceFile.read(cRightInfo1);
        int idx = 0;
        for (int i = 0; i < 1024; i++)
        {
            if (cRightInfo1[i] != 0)
            {
                cRightInfo2[idx++] = cRightInfo1[i];
            }
        }*/

        //表示两个32位的头
        byte[] MainFlag = new byte[MAX_CIPHER_MAIN_FLAG];
        byte[] FlagEncData = new byte[MAX_CIPHER_MAIN_FLAG];
        byte[] unknown = new byte[149];

        sourceFile.read(MainFlag);
        sourceFile.seek(32);
        sourceFile.read(FlagEncData);
        sourceFile.seek(64);
        sourceFile.read(unknown);

        int dwRetVal = 32;

        bytearray HEADER_FLAGENCDATA = seal.new bytearray(MAX_CIPHER_MAIN_FLAG);
        for (int i = 0; i < MAX_CIPHER_MAIN_FLAG; i++) {
            HEADER_FLAGENCDATA.a[i] = gConstCipherFlagBuffer.a[i];
        }
        seal.seal_encrypt(sc, HEADER_FLAGENCDATA, dwRetVal, 0x00);


        //对两个开头进行判断
        for (int i = 0; i < MAX_CIPHER_MAIN_FLAG; i++) {
            if (MainFlag[i] != gConstCipherFlagBuffer.a[i]) {
                return -3;
            }

            if (FlagEncData[i] != HEADER_FLAGENCDATA.a[i]) {
                return -3;
            }
        }

        /**
         * 原本的代码在这里会出现乱码，直接return，所以进行改正
         * 查明原因，这个是解密的代码，需要文件头部包含sRightInfoList这个字符串的值才能解密
         *
         * 2020.03.27 未来修改
         * 这个indexOf的判定有点奇怪，sRightInfoList是一个小的字符串，sRightInfo是大的字符串
         * 应该从大的字符串中寻找小的
         * 所以进行了修改，把两个变量的位置替换了一下
         *
         * 2020.04.01 未来修改
         * 加入新的判断
         * 1.判断前32位是否是规定的头
         * 2.判断33位到64位是否是加密过的头
         * 3.判断key是否对应
         */

        /*String sRightInfo = new String(cRightInfo2);
        sRightInfo = sRightInfo.substring(0, idx);
        if (sRightInfo.length() > 0)
        {
            if (sRightInfo.indexOf(sRightInfoList) == -1)
            {
                //源文件没有包含对应的密码
                sourceFile.close();
                return -3;
            }

        }*/

        try {
            OutputStream fos = new FileOutputStream(destination);
            BufferedOutputStream bos = new BufferedOutputStream(fos, BUFFER_SIZE);

            sourceFile.seek(ENCFILE_HEAD_LEN);

            long nLeftLen = nFileLen - ENCFILE_HEAD_LEN;
            long OffsetValue = ENCFILE_HEAD_LEN;
            bytearray FileBuf = seal.new bytearray(BUFFER_SIZE);
            byte[] bytes = new byte[BUFFER_SIZE];
            while (nLeftLen > 0) {
                int nRead = sourceFile.read(bytes);
                if (nRead <= 0)
                    break;

                nLeftLen = nLeftLen - nRead;

                for (int i = 0; i < nRead; i++)
                    FileBuf.a[i] = bytes[i];

                seal.seal_decrypt(sc, FileBuf, nRead, OffsetValue);
                for (int i = 0; i < nRead; i++)
                    bytes[i] = FileBuf.a[i];

                bos.write(bytes, 0, nRead);
                bos.flush();

                OffsetValue += nRead;
            }

            sourceFile.close();

            bos.close();
            fos.close();

            return 1;
        } catch (Exception e) {
            e.printStackTrace();
            return -4;
        }
    }
}
